package org.lightdi.container.impl;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.lightdi.container.DIContainer;
import org.lightdi.container.config.ComponentConfig.Arg;
import org.lightdi.container.config.ComponentConfig.InitMethod;
import org.lightdi.container.meta.MetaComponent;
import org.lightdi.container.meta.MetaComponentRef;
import org.lightdi.container.security.ContainerLockKey;
import org.lightdi.container.util.MetaComponentUtil;
import org.lightdi.util.ArrayUtil;
import org.lightdi.util.ReflectionUtil;

public class LightDIContainerImpl implements DIContainer {

	private String name = "DefaultContainer";
	private List<MetaComponent> metaComponents = new ArrayList<MetaComponent>();
	private Map<String, Object> singletonComponents = new ConcurrentHashMap<String, Object>();
	private ContainerLockKey key = null;

	@SuppressWarnings("unchecked")
	public <T> T getComponent(String componentName) {
		if (singletonComponents.get(componentName) != null) {
			// singleton instance type
			return (T) singletonComponents.get(componentName);
		} else {
			// prototype instance type
			Object prototypeComponent = null;
			MetaComponent metaComponent = MetaComponentUtil.getMetaComponent(
					metaComponents, componentName);

			// component has not been registered
			if (metaComponent == null)
				throw new IllegalStateException(
						"The component has not been registered. (component name: "
								+ componentName + ")");

			// ********** create constructor and invoke **********
			// constructor
			Object[] initArgs = null;
			List<Object> argValues = metaComponent.getConstructorArgValues();
			List<Class<?>> argTypes = metaComponent.getConstructorArgTypes();

			// specified value attribute
			if (metaComponent.getValue() != null) {
				// component is a simple Object
				// not required get several constructor args
				Class<?> retClass = null;
				try {
					retClass = Class.forName(metaComponent.getClassName());
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
					throw new IllegalStateException(
							"Component load error! (at: loading component class)");
				}
				return (T) ReflectionUtil.instantiate(retClass, metaComponent
						.getValue());
			}

			// constructor args
			initArgs = new Object[argValues.size()];
			int conLen = initArgs.length;
			for (int i = 0; i < conLen; i++) {
				// ref
				if (argValues.get(i) instanceof MetaComponentRef) {
					MetaComponentRef ref = (MetaComponentRef) argValues.get(i);
					initArgs[i] = getComponent(ref.getName());
				} else {
					Class<?> clazz = argTypes.get(i);
					initArgs[i] = ReflectionUtil.instantiate(clazz, argValues
							.get(i));
				}
			}
			try {
				prototypeComponent = metaComponent.getConstructor()
						.newInstance((Object[]) initArgs);
			} catch (Exception e) {
				throw new IllegalStateException(
						"Component load error! (at: component constructor invoke)");
			}

			// ********** create setter and invoke **********
			List<String> setterNames = metaComponent.getSetterNames();
			List<Class<?>> setterTypes = metaComponent.getSetterArgTypes();
			List<Object> setterValues = metaComponent.getSetterArgValues();

			// referred component
			int setLen = setterNames.size();
			for (int i = 0; i < setLen; i++) {
				if (setterValues.get(i) instanceof MetaComponentRef) {
					MetaComponentRef ref = (MetaComponentRef) setterValues
							.get(i);
					setterValues.set(i, getComponent(ref.getName()));
				}
			}

			try {
				Class<?> componentClazz = Class.forName(metaComponent
						.getClassName());
				for (int i = 0; i < setLen; i++) {
					String setterName = ReflectionUtil
							.getSetterMethodName(setterNames.get(i));
					Class<?>[] argTypeArr = ArrayUtil.toArray(setterTypes);
					Method setter = componentClazz.getMethod(setterName,
							argTypeArr);
					setter.invoke(prototypeComponent, ArrayUtil
							.toArray(setterValues));
				}
			} catch (Exception e) {
				throw new IllegalStateException(
						"Component load error! (at: setter injection");
			}

			// ********** create init-method and invoke **********
			List<InitMethod> initMethods = metaComponent.getInitMethods();
			try {
				Class<?> componentClazz = Class.forName(metaComponent
						.getClassName());
				for (InitMethod initMethod : initMethods) {
					String methodName = initMethod.getMethodName();
					List<Arg> argConfigs = initMethod.getArgs();
					List<Class<?>> parameterTypes = new ArrayList<Class<?>>();
					List<Object> parameterValues = new ArrayList<Object>();
					for (Arg argConfig : argConfigs) {
						if (argConfig.getRef() != null) {
							Object ref = getComponent(argConfig.getRef()
									.getName());
							parameterTypes.add(ref.getClass());
							parameterValues.add(ref);
						} else {
							String className = argConfig.getClassName();
							Class<?> argType = Class.forName(className);
							parameterTypes.add(argType);
							String strValue = argConfig.getValue();
							parameterValues.add(ReflectionUtil.instantiate(
									argType, strValue));
						}
					}
					Method invokeMethod = componentClazz.getMethod(methodName,
							ArrayUtil.toArray(parameterTypes));
					invokeMethod.invoke(prototypeComponent, ArrayUtil
							.toArray(parameterValues));
				}
			} catch (Exception e) {
				throw new IllegalStateException(
						"Component load error! (at: init-method invoke");
			}

			return (T) prototypeComponent;
		}
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> getComponents() {
		List<Object> components = new ArrayList<Object>();
		for (MetaComponent metaComponent : metaComponents) {
			Object value = getComponent(metaComponent.getName());
			components.add(value);
		}
		return (List<T>) components;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void lock(ContainerLockKey key) {
		if (this.key == null)
			this.key = key;
		else
			throw new SecurityException(
					"The container has been already locked! : " + name);
	}

	public void unlock(ContainerLockKey key) {
		if (this.key == null)
			throw new SecurityException("The container has not been locked! : "
					+ name);

		String passKey = key.getPassKey();
		if (passKey.equals(this.key.getPassKey()))
			this.key = null;
		else
			throw new SecurityException(
					"Cannot unlock the container!(Invalid passKey) : " + name);
	}

	public boolean isLocked() {
		return this.key != null ? true : false;
	}

	public List<MetaComponent> getMetaComponents() {
		return metaComponents;
	}

	public void setMetaComponents(List<MetaComponent> metaComponents) {
		this.metaComponents = metaComponents;
	}

	@SuppressWarnings("unchecked")
	public <T> Map<String, T> getSingletonComponents() {
		return (Map<String, T>) singletonComponents;
	}

	@SuppressWarnings("unchecked")
	public <T> void setSingletonComponents(Map<String, T> singletonComponents) {
		this.singletonComponents = (Map<String, Object>) singletonComponents;
	}

}
